﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DDD;
using Xunit;

using Vector3 = DDD.Vector3<float>;

namespace DokiDokiTest {
    public class TestNode {

        [Fact]
        public void Test_New () {
            var nod = new Node ();

            Assert.Equal (null, nod.Root);
            Assert.Equal (null, nod.Parent);
            Assert.Equal (0, nod.ChildCount);
            Assert.Equal (false, nod.IsFreezed);
        }

        [Fact]
        public void Test_AddChild () {
            var nod = new Node ();
            var child = new Node ();
            nod.AddChild (child);

            Assert.Equal (1, nod.ChildCount);
            Assert.ReferenceEquals (child, nod.GetChild (0));
        }

        [Fact]
        public void Test_RemoveChild () {
            var nod = new Node ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            nod.AddChild (ch1);
            nod.AddChild (ch2);

            nod.RemoveChild (ch1);
            Assert.Equal (1, nod.ChildCount);

            nod.RemoveChild (ch2);
            Assert.Equal (0, nod.ChildCount);
        }

        [Fact]
        public void Test_GetChild () {
            var nod = new Node ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            nod.AddChild (ch1);
            nod.AddChild (ch2);

            Assert.Equal (2, nod.ChildCount);
            Assert.Equal (ch1, nod.GetChild (0));
            Assert.Equal (ch2, nod.GetChild (1));
        }

        [Fact]
        public void Test_Parent () {
            var nod = new Node ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            nod.AddChild (ch1);
            nod.AddChild (ch2);
            Assert.Equal (nod, ch1.Parent);
            Assert.Equal (nod, ch2.Parent);
        }

        [Fact]
        public void Test_Root () {
            var wld = new World ();
            var nod = new Node ();
            var ch = new Node ();

            wld.AddChild (nod);
            wld.AddChild (ch);

            var root = wld.Root;

            // メモ：.Equalsだと配列(IEnumerable<T>)の比較が呼ばれてしまうので
            Assert.ReferenceEquals (wld, wld.Root);
            Assert.ReferenceEquals (wld, nod.Root);
            Assert.ReferenceEquals (wld, ch.Root);
        }

        /// <summary>
        /// ワールド座標変換行列(法線用)のテスト
        /// </summary>
        [Fact]
        public void Test_LcalToWorld3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix3x3 (1, 0, 0,
                                          0, 0.5f, 0,
                                          0, 0, 0.3333333f);

            Assert.Equal (expected, nod1.LocalToWorld3x3);
        }

        /// <summary>
        /// ペアレント座標変換行列のテスト
        /// </summary>
        [Fact]
        public void Test_LocalToParent () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix4x4 (1, 0, 0, 1,
                                          0, 2, 0, 4,
                                          0, 0, 3, 9,
                                          0, 0, 0, 1);

            Assert.Equal (expected, nod2.LocalToWorld);
        }

        /// <summary>
        /// ペアレント座標変換行列のテスト(法線用)
        /// </summary>
        [Fact]
        public void Test_LocalToParent3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix4x4 (1, 0, 0, -1,
                                          0, 0.5f, 0, -2,
                                          0, 0, 0.3333333f, -3,
                                          0, 0, 0, 1);

            Assert.Equal (expected, nod2.WorldToLocal);
        }


        /// <summary>
        /// ワールド座標変換行列のテスト
        /// </summary>
        [Fact]
        public void Test_LocalToWorld () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix4x4 (1, 0, 0, 1,
                                          0, 2, 0, 4,
                                          0, 0, 3, 9,
                                          0, 0, 0, 1);

            Assert.Equal (expected, nod2.LocalToWorld);
        }

        /// <summary>
        /// ワールド座標変換行列のテスト(法線用)
        /// </summary>
        [Fact]
        public void Test_LocalToWorld3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix3x3 (1, 0, 0,
                                          0, 0.5f, 0,
                                          0, 0, 0.3333333f);

            Assert.Equal (expected, nod2.LocalToWorld3x3);
        }

        /// <summary>
        /// カメラ変換行列のテスト
        /// </summary>
        [Fact]
        public void Test_LocalToCamera () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var cam = new Camera ();
            cam.SetPerspective (45, 1, 1, 100);
            nod2.Attach (cam);

            wld.AddChild (nod1);
            wld.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);
            wld.SetActiveCamera (nod2);

            var expected = new Matrix4x4 (1, 0, 0, -1,
                                          0, 2, 0, -2,
                                          0, 0, 3, -3,
                                          0, 0, 0, 1);

            Assert.Equal (expected, nod1.LocalToCamera);
        }


        /// <summary>
        /// カメラ変換行列(法線用)のテスト
        /// </summary>
        [Fact]
        public void Test_LocalToCamera3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var cam = new Camera ();
            cam.SetPerspective (45, 1, 1, 100);
            nod2.Attach (cam);

            wld.AddChild (nod1);
            wld.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod1.Rotate (30, 0, 1, 0);
            nod2.Translate (1, 2, 3);
            wld.SetActiveCamera (nod2);

            var expected = new Matrix3x3 (0.8660254f, 0, 0.1666667f,
                                          0, 0.5f, 0,
                                          -0.5000001f, 0, 0.2886752f);

            Assert.Equal (expected, nod1.LocalToCamera3x3);
        }

        /// <summary>
        /// カメラ・ローカル変換行列のテスト
        /// </summary>
        [Fact]
        public void Test_CameraToLocal () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var cam = new Camera ();
            cam.SetPerspective (45, 1, 1, 100);
            nod2.Attach (cam);

            wld.AddChild (nod1);
            wld.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);
            wld.SetActiveCamera (nod2);

            var expected = new Matrix4x4 (1, 0, 0, 1,
                                          0, 0.5f, 0, 1,
                                          0, 0, 0.3333333f, 1,
                                          0, 0, 0, 1);

            Assert.Equal (expected, nod1.CameraToLocal);
        }


        /// <summary>
        /// カメラ・ローカル変換行列のテスト(法線用)
        /// </summary>
        [Fact]
        public void Test_CameraToLocal3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var cam = new Camera ();
            cam.SetPerspective (45, 1, 1, 100);
            nod2.Attach (cam);

            wld.AddChild (nod1);
            wld.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod1.Rotate (30, 0, 1, 0);
            nod2.Translate (1, 2, 3);
            wld.SetActiveCamera (nod2);

            var expected = new Matrix3x3 (0.8660254f, 0, -0.5f,
                                          0, 2, 0,
                                          1.5f, 0, 2.598076f);

            Assert.Equal (expected, nod1.CameraToLocal3x3);
        }

        /// <summary>
        /// プロジェクション座標変換行列のテスト
        /// </summary>
        [Fact]
        public void Test_LocalToProjection () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var cam = new Camera ();
            cam.SetPerspective (45, 1, 1, 100);
            nod2.Attach (cam);

            wld.AddChild (nod1);
            wld.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);
            wld.SetActiveCamera (nod2);

            var expected = new Matrix4x4 (2.414213f, 0, 0, -2.414213f,
                                          0, 4.828427f, 0, -4.828427f,
                                          0, 0, -3.060606f, 1.040404f,
                                          0, 0, -3f, 3);

            Assert.Equal (expected, nod1.LocalToProjection);
        }

        /// <summary>
        /// プロジェクション座標変換行列のテスト(法線用)
        /// </summary>
        [Fact]
        public void Test_LocalToProjection3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var cam = new Camera ();
            cam.SetPerspective (45, 1, 1, 100);
            nod2.Attach (cam);

            wld.AddChild (nod1);
            wld.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);
            wld.SetActiveCamera (nod2);

            var expected = new Matrix3x3 (0.4142136f, 0, 0,
                                          0, 0.2071068f, 0,
                                          0, 0, -0.3267327f);

            Assert.Equal (expected, nod1.LocalToProjection3x3);
        }

        /// <summary>
        /// ローカル座標変換行列のテスト
        /// </summary>
        [Fact]
        public void Test_WorldToLocal () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix4x4 (1, 0, 0, -1,
                                          0, 0.5f, 0, -2,
                                          0, 0, 0.3333333f, -3,
                                          0, 0, 0, 1);

            Assert.Equal (expected, nod2.WorldToLocal);
        }

        /// <summary>
        /// ワールド・ローカル座標変換行列のテスト(法線用)
        /// </summary>
        [Fact]
        public void Test_WorldToLocal3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix3x3 (1, 0, 0,
                                          0, 2, 0,
                                          0, 0, 3);

            Assert.Equal (expected, nod2.WorldToLocal3x3);
        }

        /// <summary>
        /// ペアレント・ローカル座標変換行列のテスト
        /// </summary>
        [Fact]
        public void Test_ParentToLocal () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix4x4 (1, 0, 0, -1,
                                          0, 1, 0, -2,
                                          0, 0, 1, -3,
                                          0, 0, 0, 1);

            Assert.Equal (expected, nod2.ParentToLocal);
        }

        /// <summary>
        /// ペアレント・ローカル座標変換行列のテスト(法線用)
        /// </summary>
        [Fact]
        public void Test_ParentToLocal3x3 () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.Scale (1, 2, 3);
            nod2.Translate (1, 2, 3);

            var expected = new Matrix3x3 (1, 0, 0,
                                          0, 1, 0,
                                          0, 0, 1);

            Assert.Equal (expected, nod2.ParentToLocal3x3);
        }

        [Fact]
        public void Test_BoundingBox () {
            // not implemented
        }

        [Fact]
        public void Test_BoundingSphere () {
            // not implemented
        }

        /// <summary>
        /// 自分と下方向の全てのノードを列挙するDownwardsのテスト 
        /// </summary>
        [Fact]
        public void Test_Downwards () {
            var nod = new Node ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            nod.AddChild (ch1);
            nod.AddChild (ch2);
            ch2.AddChild (ch3);

            var nodes = nod.Downwards;
            Assert.Equal (4, nodes.Count ());
            Assert.Equal (nod, nodes.ElementAt (0));
            Assert.Equal (ch1, nodes.ElementAt (1));
            Assert.Equal (ch2, nodes.ElementAt (2));
            Assert.Equal (ch3, nodes.ElementAt (3));
        }

        /// <summary>
        /// 自分と上方向の全てのノードを列挙するUpwardsのテスト
        /// </summary>
        [Fact]
        public void Test_Upwards () {
            var nod = new Node ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            nod.AddChild (ch1);
            nod.AddChild (ch2);
            ch2.AddChild (ch3);
            var nodes = ch3.Upwards;

            Assert.Equal (3, nodes.Count ());
            Assert.Equal (ch3, nodes.ElementAt (0));
            Assert.Equal (ch2, nodes.ElementAt (1));
            Assert.Equal (nod, nodes.ElementAt (2));
        }


        [Fact]
        public void Test_CollisionShape () {
            // not implemented
        }


        [Fact]
        public void Test_Alpha () {
            var nod = new Node ();

            nod.Alpha = 0.1f;
            Assert.Equal (0.1f, nod.Alpha);
        }

        [Fact]
        public void Test_EffectiveAlpha () {
            var nod = new World ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            nod.AddChild (ch1);
            nod.AddChild (ch2);
            ch2.AddChild (ch3);

            nod.Alpha = 0.9f;
            ch1.Alpha = 0.8f;
            ch2.Alpha = 0.7f;
            ch3.Alpha = 0.6f;


            Assert.Equal (0.9f, nod.EffectiveAlpha, new FloatComparer (0.001f));
            Assert.Equal (0.72f, ch1.EffectiveAlpha, new FloatComparer (0.001f));
            Assert.Equal (0.63f, ch2.EffectiveAlpha, new FloatComparer (0.001f));
            Assert.Equal (0.378f, ch3.EffectiveAlpha, new FloatComparer (0.001f));
        }

        [Fact]
        public void Test_IsEffectiveRenderingEnabled () {
            var nod = new World ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            nod.AddChild (ch1);
            nod.AddChild (ch2);
            ch2.AddChild (ch3);

            ch2.IsRenderingEnabled = false;


            Assert.Equal (true, nod.IsEffectiveRenderingEnabled);
            Assert.Equal (true, ch1.IsEffectiveRenderingEnabled);
            Assert.Equal (false, ch2.IsEffectiveRenderingEnabled);
            Assert.Equal (false, ch3.IsEffectiveRenderingEnabled);
        }

        [Fact]
        public void Test_IsEffectivePickingEnabled () {
            var nod = new World ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            nod.AddChild (ch1);
            nod.AddChild (ch2);
            ch2.AddChild (ch3);

            ch2.IsPickingEnabled = false;

            Assert.Equal (true, nod.IsEffectivePickingEnabled);
            Assert.Equal (true, ch1.IsEffectivePickingEnabled);
            Assert.Equal (false, ch2.IsEffectivePickingEnabled);
            Assert.Equal (false, ch3.IsEffectivePickingEnabled);
        }

        [Fact]
        public void Test_IsEffectiveCollisionShape () {
            // not implemented
        }

        [Fact]
        public void Test_IsEffectiveLODEnabled () {
            var nod = new World ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            nod.AddChild (ch1);
            nod.AddChild (ch2);
            ch2.AddChild (ch3);

            ch2.IsLODEnabled = false;

            Assert.Equal (true, nod.IsEffectiveLODEnabled);
            Assert.Equal (true, ch1.IsEffectiveLODEnabled);
            Assert.Equal (false, ch2.IsEffectiveLODEnabled);
            Assert.Equal (false, ch3.IsEffectiveLODEnabled);

        }

        [Fact]
        public void Test_GlobalPosition () {
            var wld = new World ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            wld.AddChild (ch1);
            wld.AddChild (ch2);
            ch2.AddChild (ch3);

            ch2.Translate (0, 0, 2);
            ch3.Translate (2, 0, 0);

            var localPos = new float[] { 2, 0, 0 };
            var globalPos = new float[] { 2, 0, 2 };

            Assert.Equal ((float[])localPos, (float[])ch3.Translation);
            Assert.Equal ((float[])globalPos, (float[])ch3.WorldPosition);
        }

        [Fact]
        public void Test_CameraPosition () {
            var wld = new World ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            wld.AddChild (ch1);
            wld.AddChild (ch2);
            ch2.AddChild (ch3);

            var cam = new Camera ();
            ch3.Attach (cam);
            wld.SetActiveCamera (ch3);

            ch1.Translate (0, 2, 0);
            ch2.Translate (0, 0, 2);
            ch3.Translate (2, 0, 0);

            var expected1 = new Vector3 (-2, 2, -2);
            var expected2 = new Vector3 (-2, 0, 0);
            var expected3 = new Vector3 (0, 0, 0);

            Assert.Equal (expected1, ch1.CameraPosition);
            Assert.Equal (expected2, ch2.CameraPosition);
            Assert.Equal (expected3, ch3.CameraPosition);
        }

        [Fact]
        public void Test_GlobalOrientation () {

            var wld = new World ();
            var ch1 = new Node ();
            var ch2 = new Node ();
            var ch3 = new Node ();

            wld.AddChild (ch1);
            wld.AddChild (ch2);
            ch2.AddChild (ch3);

            ch2.Rotate (90, 1, 0, 0);
            ch3.Rotate (90, 0, 0, 1);

            var expectedw = new Quaternion (0, 0, 0, 0);
            var expected2 = new Quaternion (90, 1, 0, 0);
            var expected3 = new Quaternion (120, 0.5773503f, -0.5773502f, 0.5773503f);

            Assert.Equal (expectedw, wld.WorldOrientation);
            Assert.Equal (expected2, ch2.WorldOrientation);
            Assert.Equal (expected3, ch3.WorldOrientation);
        }

        [Fact]
        public void Test_DistanceFromCamera () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var cam = new Camera ();
            nod1.Attach<Camera> (cam);

            wld.AddChild (nod1);
            wld.AddChild (nod2);
            wld.SetActiveCamera (nod1);

            nod1.SetTranslation (10, 0, 0);
            nod2.SetTranslation (-10, 0, 0);

            Assert.Equal (20, nod2.DistanceFromCamera);
        }

        [Fact]
        public void Test_Search () {
            var nod1 = new Node ();
            var nod2 = new Node ();
            var nod3 = new Node ();
            nod1.AddChild (nod2);
            nod1.AddChild (nod3);

            nod1.UserID = 1;
            nod2.UserID = 2;
            nod3.UserID = 3;

            var nodes = nod1.Search (n => n.UserID == 2);

            Assert.Equal (1, nodes.Count ());
            Assert.Equal (nod2, nodes.First ());
        }

        [Fact]
        public void Test_Take () {
            var nod1 = new Node ();
            var nod2 = new Node ();
            var nod3 = new Node ();
            nod1.AddChild (nod2);
            nod1.AddChild (nod3);

            // nod1はカウントされない
            nod1.UserID = 1;
            nod2.UserID = 2;
            nod3.UserID = 3;

            var sum = 0;
            nod1.Take (n => sum += n.UserID);

            Assert.Equal (5, sum);
        }

        [Fact]
        public void Test_GetTransformTo () {
            var wld = new World ();
            var nod1 = new Node ();
            var nod2 = new Node ();
            var nod3 = new Node ();

            wld.AddChild (nod1);
            nod1.AddChild (nod2);
            nod1.AddChild (nod3);

            nod1.SetTranslation (1, 0, 0);
            nod2.SetTranslation (0, 1, 0);  // from
            nod3.SetTranslation (0, 0, 1);  // to

            var expected1 = new Matrix4x4 (1, 0, 0, 0,
                                           0, 1, 0, 1,
                                           0, 0, 1, 0,
                                           0, 0, 0, 1);

            var expected2 = new Matrix4x4 (1, 0, 0, 0,
                                           0, 1, 0, 1,
                                           0, 0, 1, -1,
                                           0, 0, 0, 1);

            Assert.Equal (expected1, nod2.GetTransformTo (nod1));
            Assert.Equal (expected2, nod2.GetTransformTo (nod3));
        }

        [Fact]
        public void Test_References () {
            var nod1 = new Node ();
            var nod2 = new Node ();
            var nod3 = new Node ();
            var nod4 = new Node ();
            nod1.AddChild (nod2);
            nod1.AddChild (nod3);
            nod2.AddChild (nod4);

            var objs = nod1.References;

            Assert.Equal (2, objs.Count ());
            Assert.Equal (true, objs.All (x => x != null));
        }

        [Fact]
        public void Test_Find () {
            var nod1 = new Node ();
            var nod2 = new Node ();
            var nod3 = new Node ();
            var nod4 = new Node ();
            nod1.AddChild (nod2);
            nod1.AddChild (nod3);
            nod2.AddChild (nod4);

            nod1.UserID = 101;
            nod2.UserID = 102;
            nod3.UserID = 103;
            nod4.UserID = 104;

            Assert.Equal (nod1, nod1.Find (101));
            Assert.Equal (nod2, nod1.Find (102));
            Assert.Equal (nod3, nod1.Find (103));
            Assert.Equal (nod4, nod1.Find (104));
        }

    }
}
